// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::io::Write;

use anyhow::Context;
use fixedbitset::FixedBitSet;
use log::{info, warn};
use petgraph::graph::NodeIndex;

use crate::{database::{deploy_stat::{DeployStats, DeployStatsError}, local::PKGS_ROOT}, graph::stat_dep_graph::DepGraph, utils::confirm};

pub async fn uninstall_pkgs(dep_graph: &DepGraph, recursive: bool, force_deployed: bool) -> anyhow::Result<()> {
    let to_uninstall = if recursive {
        dep_graph.collect_runtime_dependants(
            dep_graph.specs_range.clone().map(NodeIndex::new).collect()
        )
    } else {
        dep_graph.specs_range.clone().collect()
    };

    {
        let mut dump = Vec::new();
        if dump_uninstall_pkgs(dep_graph, &to_uninstall, recursive, &mut dump).is_ok() {
            info!("{}", String::from_utf8_lossy(&dump));
        }
    }

    if check_deploy_conflicts(dep_graph, &to_uninstall).await
       .map_err(|e| anyhow::anyhow!("failed to check deploy conflicts: {}", e))?
    {
        let resume = force_deployed || confirm("Do you want to continue?").context("failed to read input")?;
        if !resume {
            return Ok(());
        }
    }

    for id in to_uninstall.ones() {
        let spec_str = dep_graph.node(NodeIndex::new(id)).unwrap();
        tokio::fs::remove_file(PKGS_ROOT.join(format!("{}.stat", spec_str))).await
            .context(format!("failed to remove stat file for {}", spec_str))?;
        tokio::fs::remove_dir_all(PKGS_ROOT.join(spec_str)).await
            .context(format!("failed to remove pkg directory for {}", spec_str))?;
    }

    Ok(())
}

async fn check_deploy_conflicts(dep_graph: &DepGraph, to_uninstall: &FixedBitSet) -> Result<bool, DeployStatsError> {
    let mut has_conflict = false;
    for deploy_stat_dir in DeployStats::find_deploy_dirs() {
        if let Some((deploy_stats, _)) = DeployStats::load_current(&deploy_stat_dir).await? {
            for spec_str in deploy_stats.deployed_pkgs() {
                if dep_graph.node_id(spec_str).is_some_and(|id| to_uninstall.contains(id.index())) {
                    warn!("uninstall conflict: {} is deployed in {}", spec_str, deploy_stat_dir.display());
                    has_conflict = true;
                }
            }
        }
    }

    Ok(has_conflict)
}

fn dump_uninstall_pkgs<W: Write>(dep_graph: &DepGraph, to_uninstall: &FixedBitSet, recursive: bool, mut w: W) -> std::io::Result<()> {
    writeln!(w, "Uninstall pkgs:")?;
    for id in dep_graph.specs_range.clone() {
        let spec_str = dep_graph.node(NodeIndex::new(id)).unwrap();
        writeln!(w, "  - {}", spec_str)?;
    }
    if recursive {
        writeln!(w, "Auto uninstall pkgs:")?;
        for id in to_uninstall.ones() {
            if dep_graph.specs_range.contains(&id) {
                continue;
            }
    
            let spec_str = dep_graph.node(NodeIndex::new(id)).unwrap();
            writeln!(w, "  - {}", spec_str)?;
        }
    }

    Ok(())
}
